#include <iostream>
#include <sstream>
#include <string>
#include <vector>
//#include <boost/algorithm/string/split.hpp>
#include "ClientThread.h"
#include "../SharedMethods/globals.h"
#include "../SharedMethods/Exceptions.h"
using namespace std;
 

const int  REQ_WINSOCK_VER   = 2;	// Minimum winsock version required
const int  DEFAULT_PORT      = 4444;	
const int  TEMP_BUFFER_SIZE  = 128;

ClientThread::ClientThread(SOCKET hClientSocket, const sockaddr_in &sockAddr)
{

	_clientSocket = hClientSocket;
	_sockAddr = sockAddr;

}




void ClientThread::run()
{
		// Print description (IP:port) of connected client
	cout << "Connected with " << GetHostDescription(_sockAddr) << endl;

	char tempBuffer[TEMP_BUFFER_SIZE];

	try
	{
		
		while(true)
		{
			int retval;
			
			//suspend thread while waiting for a message from the client
			retval = recv(_clientSocket, tempBuffer, sizeof(tempBuffer), 0);

			if (retval==0)
			{ 
				break; // Connection has been closed
			}
			else if (retval==SOCKET_ERROR)
			{
				throw "socket error while receiving.";
			}
			else
			{
				//handle the message
				try{
					HandleMessage(tempBuffer);
				}
				catch(char *str)
				{
					cout << "error: " << str << endl;
				}

				// retval is the number of bytes received.
			//	if (send(_clientSocket, tempBuffer, retval, 0)==SOCKET_ERROR)
			//		throw ConnectionException("socket error while sending.");
			}
		}

	}
	catch (char* ex)
	{
		cerr << "\nError: " << ex << endl;
	}
	cout << "Connection closed.\n";
}



/*
Handles a message from the client, replies to the message, then, depending on the situation, 
waits for another message.
For example, if the client said he wants to send a file, we send a message back saying: "OK", then
we wait for the file, with the recv function, then we tell them we're going to send the new folder contents,
wait for a response of "OK", then send the folder contents
*/
//using namespace boost;
//throws a char* exception

void ClientThread::HandleMessage(char* msg)  
{
	string str = msg; //copy the message into an easy to use string
	vector<string>* strList;
	
	strList = split(msg, ":");

	if((*strList)[0] == "put")
	{ 
		if(strList->size() < 3)
		{
			throw "insufficient information to receive a file: missing filename and/or file size"; 
			return;
		}
		
		//get file name
		string fileName = (*strList)[1];
		long fileSize = atoi((*strList)[2].c_str());//get the file size
		//send an OK message
		int res = send(_clientSocket, "OK", sizeof("OK"), 0);
		if(res == SOCKET_ERROR)
			throw "error sending reply";

		//create properly sized buffer to receive file
		char* buff = new char[fileSize];
		//wait for receipt of file
		res = recv(_clientSocket, buff, fileSize, 0);
		
		if (res==0) 
			return; // Connection has been closed
		
		else if (res==SOCKET_ERROR)
		{
			send(_clientSocket, "fail", strlen("fail"), 0); //tell the client we failed to receive the file.
			throw "socket error while receiving file.";
		}
		//write file onto hard-drive.
		FILE* fp;
		errno_t errNo = fopen_s(&fp, ("Files\\" + fileName).c_str(), "wb");
		if(errNo != 0)
		{
			send(_clientSocket, "fail", strlen("fail"), 0); //tell the client we failed to write the file.
			throw ("Unable to open file " + fileName + "for writing").c_str();
		}
		fwrite(buff, fileSize, 1, fp);
		
		
		send(_clientSocket, "success", strlen("success"), 0);
		
	    //SendFolderContents();


		fclose(fp);
		delete buff;
	}
	else if((*strList)[0] == "get")
	{
		//if we have insufficient information to send them the file, abort and tell the user
		if(strList->size() < 2)
		{
			send(_clientSocket, "fail", 4, 0);
			return;
		}
		cout << GetHostDescription(_sockAddr) << " requested file: " << (*strList)[1].c_str() << endl;
		string strFilepath = "Files\\" + (*strList)[1];

		//try to load the file
		FILE * pFile;
		long size;
		
		fopen_s(&pFile, strFilepath.c_str(), "rb");

		if (pFile==NULL) 
		{	
		//	cout << "File does not exist." << endl;
			//Send a message back, saying that the file doesn't exist
			send(_clientSocket, "nofile", strlen("nofile"), 0);
			return;
		}
		//Send a message back with the size of the file
		//get the size of the file
		fseek (pFile, 0, SEEK_END);  
		size=ftell (pFile);
		fclose (pFile);

		std::ostringstream o;
		o << size;
		string instrBuff = string("put:") + (*strList)[1] + ":" + o.str() + ":";

		 //send the message: "put:<filename>:<size>"
		send (_clientSocket, instrBuff.c_str(), instrBuff.size (), 0);
		try
		{
			WaitForAcknowledge();
		}
		catch (char* ex)
		{
			cout << "Error: " << ex << endl;
			return;
		}
		
		//Send over the file
//--------------------------------------
		pFile = fopen (strFilepath.c_str(), "rb");
		
		char* buff = new char [size];
		fread(buff,size, 1, pFile);
		cout << "Sending file to " << GetHostDescription(_sockAddr) << ", waiting..." << endl;
		send (_clientSocket, buff, size, 0);
		fclose (pFile);
		
		//wait for success/failure from client
		bool success = false;
		try{
			success = WaitForSuccessOrFailure();
		}
		catch(ConnectionException e)
		{
			cerr << e.message() << endl;
			return;
		}
		catch(FailedToReceiveAcknowledgeException e)
		{
			cerr << "Failed to transfer file to client: " << GetHostDescription(_sockAddr) <<  ", possibly due to a problem on the client's side" << endl;
			return;
		}

		if(success)
			cout << (*strList)[1].c_str() << " Sent to client:"  <<   GetHostDescription(_sockAddr) << endl;
		else
			cout << "Failed to send: " << (*strList)[1].c_str()   <<  " to: " << GetHostDescription(_sockAddr) << endl;
	}

	delete strList;
}

void ClientThread::WaitForAcknowledge()
{
	//wait for acknowledgement
	char temp[TEMP_BUFFER_SIZE];
		
	int res = recv(_clientSocket, temp, TEMP_BUFFER_SIZE, 0);
	string str = temp;
	if (res==0) 
		return; // Connection has been closed
		
	else if (res==SOCKET_ERROR)
	{
		throw "socket error while receiving file.";
	}
	else
	{
		
		if(str.compare(0, 2, "OK") != 0)
			throw "Client failed to acknowledge with the correct protocol. Aborting file transfer.";
	}
}

void ClientThread::SendFolderContents()
{
	list<string>* fileList;
	try
	{
		list<string>* fileList = ListDirectoryContents("Files");
	}
	catch(char* ex)
	{
		cout << "Error listing directory contents: " << ex << endl;
		return;
	}
	//send one long buffer, with all of the file names, with colons in between
	//get total size of strings, with spaces in between
	//bob.txt:movie.avi:yada.pdf
	
	int size = 0;
	for(list<string>::iterator it; it != fileList->end(); it++)
	{
		size += it->size();
	}

	size += fileList->size() - 1; //acount for the colons in between filenames

	//copy the filenames into one big buffer, adding ':' in between them
	char* buff = new char[size];
	int index=0;
	bool done = false;
	for(list<string>::iterator it; it != fileList->end() || done; it++)
	{
		strcpy_s(&buff[index], it->size(), it->c_str());
		index += it->size();
		
		if(index >= size) //check for end of array
		{
			done = true;
			continue;
		}
		buff[index] = ':'; //add the delimiter
		index++;
	}
	char msg[128];
	sprintf_s(msg, size, "foldercontents %i");
	send(_clientSocket, msg, strlen(msg), 0);
	WaitForAcknowledge();

	int retval = send(_clientSocket, buff, size, 0);//send the buffer
	if(retval == SOCKET_ERROR)
		throw "Error sending file";
	
}



list<string>* ClientThread::ListDirectoryContents(const char *sDir)
{
    WIN32_FIND_DATA fdFile;
    HANDLE hFind = NULL;
    char sPath[2048];

    //Specify a file mask. *.* = We want everything!
    sprintf_s(sPath, 2048,  "%s\\*.*", sDir);

	list<string>* fileList = new list<string>();

    if((hFind = FindFirstFile(sPath, &fdFile)) == INVALID_HANDLE_VALUE)
    {
       // printf("Path not found: [%s]\n", sDir);
        throw ("Path not found: " + string(sDir)).c_str();
		return NULL;
    }

    do
    {
        //Find first file will always return "."
        //    and ".." as the first two directories.
        if(strcmp(fdFile.cFileName, ".") != 0
                && strcmp(fdFile.cFileName, "..") != 0)
        {
            //Build up our file path using the passed in
            //  [sDir] and the file/foldername we just found:
            //sprintf(sPath, "%s\\%s", sDir, fdFile.cFileName);
			

            //Is the entity a File or Folder?
            if((fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
            {
				fileList->push_back(fdFile.cFileName);
               // printf("Directory: %s\n", sPath);
               // ListDirectoryContents(sPath); //Recursion
            }
        }
    }
    while(FindNextFile(hFind, &fdFile)); //Find the next file.

    FindClose(hFind); //Always, Always, clean things up!

	return fileList;
}


//suspend thread until the other party returns a "success" or "fail"
//returns true on success, false on fail
//If the returned message is neither of these, then throw a FailedToReceiveAcknowledgeException, 
// 
bool ClientThread::WaitForSuccessOrFailure()
{
	//wait for acknowledgement
	char temp[128];
		
	int res = recv(_clientSocket, temp, 128, 0);
	if (res==0) 
		throw ConnectionException("Connection has been closed");
		
	else if (res==SOCKET_ERROR)
	{
		throw "socket error while waiting for an acknowledgment.";
	}
	else
	{
		string msg = temp;
		//if(_stricmp(temp, "success") == 0)
		if(msg.compare(0, strlen("success"), "success") == 0)
			return true;
	//	else if(_stricmp(temp, "Fail") == 0)
		else if(msg.compare(0, strlen("fail"), "fail") == 0)
			return false;
		else
			throw FailedToReceiveAcknowledgeException(temp);
	}
}

void ClientThread::Terminate()
{
	//send a message to the client, telling him that the server has disconnected
	send(_clientSocket, "disconnect", strlen("disconnect"), 0);

}